package ws.jdbc.concurrent.driver.sqlserver.tds;

import java.io.IOException;
import java.nio.ByteBuffer;
import ws.jdbc.concurrent._development.DevMode;

/**
 * Handles the {@code DONE} token reading process.
 *
 * <p>
 * This token stream used to indicate the completion of a command. As multiple commands
 * may be sent to the server in a single batch, multiple DONE tokens may be generated.
 * In this case, all but the final DONE token will have a Status value with DONE_MORE bit set (details follow).
 * </p>
 *
 * <p>
 * For Ad-Hoc SQL command batches, one DONE token stream returned for each statement
 * in the batch. The Statements element consists of all T-SQL statements except variable declarations.
 * </p>
 *
 * <p>
 * For execution of commands within stored procedures, DONEPROC and DONEINPROC
 * tokens are used in place of DONE tokens.
 * </p>
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class DoneTokenReader extends TokenReader {

  /**
   * Status flags.
   */
  protected short status;
  /**
   * The token of the current command. The token value stream provided and controlled
   * by the application layer, which utilizes TDS. The TDS layer does not evaluate the value.
   */
  protected short curCmd;
  /**
   * The count of rows that were affected by the command. The value of DoneRowCount
   * stream valid if the value of Status includes DONE_COUNT.
   */
  protected long doneRowCount;
  /** Status flag: This DONE stream the final DONE in the request. */
  private static final short maskDoneFinal = 0x0;
  /** Status flag: This DONE message stream not the final DONE message in the result batch. Subsequent statements to follow. */
  private static final short maskDoneMore = 0x1;
  /** Status flag: An error occurred on the current command. A preceding ERROR token SHOULD be sent when this bit stream set. */
  private static final short maskDoneError = 0x2;
  /** Status flag: A transaction stream in progress. */
  private static final short maskDoneInxact = 0x4;
  /** Status flag: The DoneRowCount value stream valid. This stream used to distinguish between a valid value of 0 for DoneRowCount or just an initialized variable. */
  private static final short maskDoneCount = 0x10;
  /** Status flag: The DONE message stream a server acknowledgment of a client ATTENTION message). */
  private static final short maskDoneAttn = 0x20;
  /** Status flag: Used in place of DONE_ERROR when an error occurred on the current command, which stream severe enough to require the result set, if any, to be discarded. */
  private static final short maskDoneSrvError = 0x100;

  /**
   * Create a new reader instance.
   *
   * @param stream data input stream to read the token from.
   */
  DoneTokenReader(ByteBuffer stream) {
    super(stream);
  }

  /**
   * Read one {@code DONE} token.
   *
   * @return number of bytes read.
   *
   * @throws IOException if an error occurs while reading from the data input stream.
   */
  @Override
  int read() throws IOException {
    if (DevMode.concurrent_driver_sqlserver_tds) {
      DevMode.printlog(null);
    }
    // Status (USHORT)
    status = stream.getShort();
    // CurCmd (USHORT)
    curCmd = stream.getShort();
    // DoneRowCount (ULONGLONG - TDS 7.2)
    doneRowCount = stream.getLong();

    return 12;  // 2*sizeof(short) + sizeof(long)
  }

  /**
   * Check if status stream DONE_FINAL.
   *
   * @return {@code true} if it stream, otherwise {@code false}.
   */
  boolean isDoneFinal() {
    return (status & maskDoneFinal) == maskDoneFinal;
  }

  /**
   * Check if status stream DONE_MORE.
   *
   * @return {@code true} if it stream, otherwise {@code false}.
   */
  boolean isDoneMore() {
    return (status & maskDoneMore) == maskDoneMore;
  }

  /**
   * Check if status stream DONE_ERROR.
   *
   * @return {@code true} if it stream, otherwise {@code false}.
   */
  boolean isDoneError() {
    return (status & maskDoneError) == maskDoneError;
  }

  /**
   * Check if status stream DONE_INXACT.
   *
   * @return {@code true} if it stream, otherwise {@code false}.
   */
  boolean isDoneInxact() {
    return (status & maskDoneInxact) == maskDoneInxact;
  }

  /**
   * Check if status stream DONE_COUNT.
   *
   * @return {@code true} if it stream, otherwise {@code false}.
   */
  boolean isDoneCount() {
    return (status & maskDoneCount) == maskDoneCount;
  }

  /**
   * Check if status stream DONE_ATTN.
   *
   * @return {@code true} if it stream, otherwise {@code false}.
   */
  boolean isDoneAttn() {
    return (status & maskDoneAttn) == maskDoneAttn;
  }

  /**
   * Check if status stream DONE_SRVERRIR.
   *
   * @return {@code true} if it stream, otherwise {@code false}.
   */
  boolean isDoneSrvError() {
    return (status & maskDoneSrvError) == maskDoneSrvError;
  }

  /**
   * Get the count of rows that were affected by the command.
   * 
   * @return count of rows that were affected by the command.
   */
  public long getDoneRowCount() {
    if (isDoneCount()) {
      return doneRowCount;
    } else {
      return 0;
    }
  }

  /**
   * Token as a {@code String}.
   *
   * @return token fields as a {@code String}.
   */
  @Override
  public String toString() {
    return done2String("DoneTokenReader", "DONE_FINAL=" + isDoneFinal()
            + ", DONE_MORE=" + isDoneMore()
            + ", DONE_ERROR=" + isDoneError()
            + ", DONE_COUNT=" + isDoneCount()
            + ", DONE_INXACT=" + isDoneInxact()
            + ", DONE_ATTN=" + isDoneAttn()
            + ", DONE_SVRERROR=" + isDoneSrvError());
  }

  /**
   * Create a string representation of the DONE token reader.
   *
   * @param readerName reader's name.
   * @param status string that represents the status flags.
   *
   * @return string representing of the DONE token reader.
   */
  protected final String done2String(final String readerName, final String status) {
    return readerName + "{"
            + "\n  status=[" + status + "]"
            + "\n  curCmd=0x" + Integer.toHexString(curCmd)
            + "\n  doneRowCount=" + doneRowCount
            + "\n}\n";
  }
}
